package com.efrei.uchat.message;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.efrei.uchat.exception.UChatException;

/**
 * Raw message class
 * @author pierrick
 */
public class Message {
	
	/**
	 * attributes
	 */
	private Headers type;
	private int sessionId;
	/**
	 * fields with raw format
	 */
	protected List<String> fields = new ArrayList<String>();
	protected final static char MESSAGE_SEPARATOR = '#';

	/**
	 * default constructor with unknown message and sessionId
	 */
	public Message() {
		type = Headers.UNKNOWN;
		sessionId = 0;
	}
	
	/**
	 * init raw message with type + sessionId
	 * @param type message type
	 * @param sessionId
	 */
	public Message(Headers type, int sessionId) {
		this.type = type;
		this.sessionId = sessionId;
	}
	
	/**
	 * init raw message from his serialized representation
	 * @param text
	 * @throws UChatException
	 * @Deprecated see {@link Message#fromString(String) generic constructor}
	 */
	public Message(String text) throws UChatException {
		String[] data = parseMessage(text);
		
		this.sessionId = Integer.valueOf(data[0]);
		this.type = Headers.getFromText(data[1]);
		
		for(int i=2; i<data.length; i++) {
			fields.add(data[i]);
		}
	}
	
	/**
	 * static method to convert serialized representation to typed Message
	 * @param text
	 * @return
	 * @throws UChatException
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static Message fromString(String text) throws UChatException {
		//System.out.println("DEBUG: parse "+text);
		String[] data = parseMessage(text);
		
		// constructor with raw string
		Class[] constructorArgs = new Class[]{String.class};
		try {
			// get constructor from class associated with this type
			Constructor ct = Headers.getFromText(data[1])
					.getClassManager().getDeclaredConstructor(constructorArgs);
			// initiate new Message with this constructor
			return (Message) ct.newInstance(text);
		} catch(UChatException e) {
			throw e;
		} catch(Exception e) {
			throw new UChatException("Invalid Headers class with this type", e);
		}
	}

	@Override
	public String toString() {
		StringBuilder s = new StringBuilder();
		s.append(MESSAGE_SEPARATOR).append(sessionId).append(MESSAGE_SEPARATOR)
				.append(type);
		for (String field : fields) {
			s.append(MESSAGE_SEPARATOR).append(field);
		}
		s.append(MESSAGE_SEPARATOR);
		return s.toString();
	}

	/**
	 * return field from message representation with
	 * <ul>
	 * <li>userid</li>
	 * <li>messagetype</li>
	 * <li>custom message fields</li>
	 * </ul>
	 * @param index
	 * @return
	 * @throws UChatException
	 */
	protected String getField(int index) {
		int nbField = getNbFields();
		if (index >= nbField || index < 0)
			throw new IllegalArgumentException("Invalid index field");
		
		switch (index) {
		case 0:
			return String.valueOf(sessionId);
		case 1:
			return type.toString();
		default:
			return fields.get(index - 2);
		}
	}

	/**
	 * final message representation fields size (with id+type)
	 * @return
	 */
	protected int getNbFields() {
		return 2 + fields.size();
	}

	/**
	 * split string with message separator
	 * @param string
	 * @return message's fields
	 */
	protected static String[] parseMessage(String text) {
		if(text.endsWith("\n"))
			text = text.substring(0, text.length()-1);
		
		// check if text contains EOF except end of string
		if(text.substring(0, text.length()).contains("\n")) {
			// resplit string with EOF replace by separator value
			text = text.replace('\n', MESSAGE_SEPARATOR);
		}
		
		List<String> fields = new ArrayList<String>();
		String field;
		int last = 1;
		int length = text.length();
		for(int i=1; i<length; i++) {
			if(text.charAt(i)==MESSAGE_SEPARATOR) {
				if(i==last) {
					// ignore empty line
					//logger.error("ignore empty line at "+i);
					continue;
				}
				// add this field
				field = text.substring(last, i);
				// replace ## by #
				field = field.replace(
						String.format("\\%s", MESSAGE_SEPARATOR),
						String.valueOf(MESSAGE_SEPARATOR));
				fields.add(field);
				// take last character as next string's begin
				last = i+1;
				i++;
			}
		}
		// List<String> to String[]
		return Arrays.copyOf(fields.toArray(), fields.size(), String[].class);
	}
	
	/**
	 * set internal fields list with custom message serializion (without id and type)
	 * @param s
	 */
	protected void setFields(String s) {
		// split with \n as separator
		String[] data = s.split("\n");
		// and store in fields
		fields.clear();
		for (String string : data) {
			fields.add(string);
		}
	}
	
	public int getSessionId() {
		return sessionId;
	}
	
	public Headers getType() {
		return type;
	}
	
	public void setSessionId(int sessionId) {
		this.sessionId = sessionId;
	}
	
	public static void main(String[] args) throws UChatException {
		Message msg = new Message("#0#TEXT#hello world#");
		System.out.println("message: "+msg.toString());
		
		msg = new Message("#0#TEXT#hello # world#");
		System.out.println("message: '"+msg.toString()+"'");
		
		msg = new Message("#0#TEXT#mon message \n multiligne :)#");
		System.out.println("message: '"+msg.toString()+"'");
	}
}
